

#include <stdio.h>
#include <string.h>
#include <assert.h>

#include "raise.h"
#include "class.h"
#include "string.h"
#include "container.h"
#include "new.h"

#define INITIAL_STRING_SIZE 16


BEGIN_CLASS(String);
    char* _str;
    size_t _size;
    size_t _buf_size;
END_CLASS(String);

static void String_ctor(StringClass* this, va_list* args)
{
    char* s = va_arg(*args, char*);
    if (s != NULL)
    {
        this->_str = strdup(s);
        this->_size = strlen(this->_str);
        this->_buf_size = this->_size + 1;
    } // default are NULL, 0, 0
}

static void String_dtor(StringClass* this)
{
    free(this->_str);
}

static void String_clear(StringClass* this)
{
    if (this->_buf_size > 4 * INITIAL_STRING_SIZE)
    {
        free(this->_str);
        this->_str = NULL;
        this->_buf_size = 0;
    }
    this->_size = 0;
}

static char const* String_str(StringClass const* this)
{
    if (this->_str == NULL)
        return "";
    return this->_str;
}

static Object* String_add(StringClass const* this, StringClass const* other)
{
    assert(this != NULL && "this is NULL");
    assert(other != NULL && "other is NULL");
    StringClass* res = new(String, NULL);
    if ((res->_size = this->_size + other->_size) == 0)
        return res;
    res->_buf_size = res->_size + 1;
    res->_str = calloc(1, res->_buf_size);
    if (res->_str == NULL)
        raise("Out of memory");
    memcpy(res->_str, this->_str, this->_size);
    memcpy(res->_str + this->_size, other->_str, other->_size);
    res->_str[res->_size] = '\0';
    return res;
}

static bool String_eq(StringClass const* this, StringClass const* other)
{
    assert(this != NULL && "this is NULL");
    assert(other != NULL && "other is NULL");
    if (this->_size == other->_size)
        return (this->_size == 0 || strncmp(this->_str, other->_str, this->_size) == 0);
    return false;
}

static bool String_lt(StringClass const* this, StringClass const* other)
{
    assert(this != NULL && "this is NULL");
    assert(other != NULL && "other is NULL");
    return strcmp(String_str(this), String_str(other)) < 0;
}

static bool String_gt(StringClass const* this, StringClass const* other)
{
    assert(this != NULL && "this is NULL");
    assert(other != NULL && "other is NULL");
    return strcmp(String_str(this), String_str(other)) > 0;
}

static char const* String_front(StringClass const* this)
{
    if (this->_size == 0)
        raise("front(): String is empty");
    return &(this->_str[0]);
}

static char const* String_back(StringClass const* this)
{
    if (this->_size == 0)
        raise("front(): String is empty");
    return &(this->_str[this->_size - 1]);
}

static void    String_push_back(StringClass* this, va_list* ap)
{
    char c = va_arg(*ap, int);
    if (this->_str == NULL)
        this->_str = malloc(INITIAL_STRING_SIZE * sizeof(char));
    else if (this->_size + 1 >= this->_buf_size)
    {
        this->_buf_size += this->_size;
        this->_str = realloc(this->_str, this->_buf_size);
    }
    if (this->_str == NULL)
        raise("Out of memory");
    this->_str[this->_size++] = c;
    this->_str[this->_size] = '\0';
}

static size_t String_size(StringClass* this)
{
    return this->_size;
}

static void String_setitem(StringClass* this, va_list* ap)
{
    size_t index = va_arg(*ap, size_t);
    char c = va_arg(*ap, int);
    if (index >= this->_size)
    {
        this->_size = index + 1;
        if (this->_size >= this->_buf_size)
        {
            this->_buf_size = this->_size + 1;
            if (this->_str == NULL)
                this->_str = malloc(this->_buf_size);
            else
                this->_str = realloc(this->_str, this->_buf_size);
            if (this->_str == NULL)
                raise("Out of memory");
        }
        this->_str[this->_size] = '\0';
    }
    this->_str[index] = c;
}

static char const* String_getitem(StringClass const* this, va_list* ap)
{
    size_t index = va_arg(*ap, size_t);
    if (index >= this->_size)
        raise("Out of range");
    return &(this->_str[index]);
}

static Object* String_begin(Object* this)
{
    (void) this;
    raise("Not implemented");
    return NULL;
}

static Object* String_end(Object* this)
{
    (void) this;
    raise("Not implemented");
    return NULL;
}


IMPL_CONTAINER_CLASS(
    String,
    &String_ctor, &String_dtor, &String_str,
    &String_add, NULL, NULL, NULL,
    &String_eq, &String_gt, &String_lt, // eq, gt, lt
    NULL, //push_front
    &String_push_back,
    NULL, // pop_front
    NULL, // pop_back
    NULL, // append
    NULL, // erase
    &String_clear,
    &String_front,
    &String_back,
    &String_size,
    &String_setitem,
    &String_getitem,
    &String_begin,
    &String_end,
    NULL,   // _str
    0,      // _size
    0       // _buf_size
);

